﻿/////////////////////////////////////////////////////////////////
//
// Silverlight 5: 3D Physics using Balder and Jiglibx
// by Andy Beaulieu - http://www.andybeaulieu.com
//
// LICENSE: This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS
// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
/////////////////////////////////////////////////////////////////
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Balder.Execution;
using System.Collections.Generic;
using Balder.Objects.Geometries;
using JigLibX.Physics;
using JigLibX.Collision;
using Balder;
using System.Text;
using Balder.Materials;
using System.Windows.Media.Imaging;
using Balder.Imaging;
using Balder.Math;

namespace Spritehand.Physics3D
{
    public class MyGame : Game
    {
        // INPUT
        const double _lookSpeed = 0.5d;
        const double _moveSpeed = 0.5d;
        Point _ptMouse;
        bool _isDragging;
        DateTime _timeLastFire;

        // CAMERA
        Balder.Math.Coordinate _cameraStart;
        Camera _cameraController;

        // PHYSICS
        const float _updateStep = 0.04f;
        PhysicsSystem _world = new PhysicsSystem();

        // OBJECTS
        int _numBoxesPerRow = 8;
        int _numBoxRows = 6;
        public HeightMapInfo _heightMapInfo;
        List<IPhysicsObject> _physicsObjects = new List<IPhysicsObject>();
        PhysicsSphere _spherePlayer;
        Container _container = new Container();

        // STATISTICS
        GeometryStatistics _stats;
        DateTime _timeLastStatCheck;
        TextBlock _tbStats;

        public enum ProjectileType
        {
            Capsule,
            Sphere,
            Car,
            Ragdoll,
            Crate
        }

        public ProjectileType ProjectileSelected { get; set; }

        public override void OnInitialize()
        {
            ProjectileSelected = ProjectileType.Capsule;

            _world.CollisionSystem = new CollisionSystemSAP();
            _world.EnableFreezing = true;
            _world.SolverType = PhysicsSystem.Solver.Normal;
            _world.CollisionSystem.UseSweepTests = true;

            _world.NumCollisionIterations = 8;
            _world.NumContactIterations = 8;
            _world.NumPenetrationRelaxtionTimesteps = 15;

            // add boxes
            double offset = 1.5;
            for (int i = 0; i < _numBoxRows; i++)
            {
                for (int j = 0; j < _numBoxesPerRow; j++)
                {
                    double x = offset + ((j - (_numBoxesPerRow / 2)) * 3);
                    double y = offset + (3 * i);
                    AddABox(new Balder.Math.Coordinate(x, y, 3), new Balder.Math.Coordinate(3, 3, 3));
                }

                _numBoxesPerRow--;

            }

            // a sphere
            _spherePlayer = new PhysicsSphere();
            _spherePlayer.Radius = 3;
            _spherePlayer.Position = new Balder.Math.Coordinate(0, 10, -10);
            _spherePlayer.InitPhysics(_world);
            _physicsObjects.Add(_spherePlayer);
            _container.Children.Add(_spherePlayer);

            // a car
            AddACar(new Balder.Math.Coordinate(0, 0, 20));


            System.Windows.Controls.Image imgMapData = this.FindName("imgMapData") as System.Windows.Controls.Image;
            _heightMapInfo = Utils.GetHeightMapInfo(imgMapData);
            // terrain (heightmap)
            Heightmap heightMap1 = this.FindName("heightMap1") as Heightmap;
            PhysicsHeightMap heightmapObj = new PhysicsHeightMap(_world, heightMap1, _heightMapInfo, Microsoft.Xna.Framework.Vector2.Zero);
            imgMapData.Visibility = Visibility.Collapsed;

            _physicsObjects.Add(heightmapObj);

            heightMap1.Material = Materials.Sand;
            IGeometryContext gcontext = heightMap1.GeometryContext;
            IGeometryDetailLevel context = gcontext.GetDetailLevel(Balder.Rendering.DetailLevel.Full);
            //context.AllocateTextureCoordinates(12);
            // texture mapping
            //context.SetTextureCoordinate(0, new TextureCoordinate(0, 1));
            //context.SetTextureCoordinate(1, new TextureCoordinate(1, 1));
            //context.SetTextureCoordinate(2, new TextureCoordinate(1, 0));
            //context.SetTextureCoordinate(3, new TextureCoordinate(0, 0));

            //context.SetFaceTextureCoordinateIndex(0, 0, 1, 2);
            //context.SetFaceTextureCoordinateIndex(1, 0, 2, 3);
            //context.SetFaceTextureCoordinateIndex(2, 0, 1, 2);
            //context.SetFaceTextureCoordinateIndex(3, 0, 2, 3);
            //context.SetFaceTextureCoordinateIndex(4, 0, 1, 2);
            //context.SetFaceTextureCoordinateIndex(5, 0, 2, 3);
            //context.SetFaceTextureCoordinateIndex(6, 2, 1, 0);
            //context.SetFaceTextureCoordinateIndex(7, 3, 2, 0);
            //context.SetFaceTextureCoordinateIndex(8, 2, 1, 0);
            //context.SetFaceTextureCoordinateIndex(9, 3, 2, 0);
            //context.SetFaceTextureCoordinateIndex(10, 2, 1, 0);
            //context.SetFaceTextureCoordinateIndex(11, 3, 2, 0);



            this.Children.Add(_container);

            _tbStats = new TextBlock();
            _tbStats.Foreground = new SolidColorBrush(System.Windows.Media.Colors.White);
            _tbStats.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
            this.Children.Add(_tbStats);

            _cameraStart = this.Camera.Position;
            _cameraController = new Physics3D.Camera(this.Camera);

            _stats = new GeometryStatistics();

        }

        public void Heightmap_HeightInput(object sender, Balder.Objects.Geometries.HeightmapEventArgs e)
        {

            e.Height = 0.45f * _heightMapInfo.heights[e.GridX, Convert.ToInt32(_heightMapInfo.heightmapHeight) - e.GridY];

        }


        private void FireProjectile()
        {
            if ((DateTime.Now - _timeLastFire).TotalMilliseconds > 500)
            {

                IPhysicsObject projectile;
                Balder.Math.Coordinate vel;

                switch (ProjectileSelected)
                {
                    case ProjectileType.Car:
                        projectile = AddACar(this.Camera.Position);
                        vel = (this.Camera.Target - this.Camera.Position) * 150;
                        break;
                    case ProjectileType.Crate:
                        projectile = AddABox(this.Camera.Position, new Balder.Math.Coordinate(3, 3, 3));
                        vel = (this.Camera.Target - this.Camera.Position) * 50;
                        break;
                    case ProjectileType.Ragdoll:
                        projectile = AddARagdoll(this.Camera.Position);
                        vel = (this.Camera.Target - this.Camera.Position) * 150;
                        break;
                    case ProjectileType.Sphere:
                        projectile = AddASphere(this.Camera.Position, 1.0f);
                        vel = (this.Camera.Target - this.Camera.Position) * 50;
                        break;
                    default:
                        projectile = AddACapsule(this.Camera.Position);
                        vel = (this.Camera.Target - this.Camera.Position) * 50;
                        break;
                }


                projectile.PhysicsBody().ApplyBodyImpulse(new Microsoft.Xna.Framework.Vector3((float)vel.X, (float)vel.Y, (float)vel.Z));

                _timeLastFire = DateTime.Now;

            }
        }

        public override void OnUpdate()
        {
            UpdatePhysics();

            if (InputManager.IsKeyDown(Key.Enter))
            {
                FireProjectile();
            }

            //Update Camera
            _cameraController.Update(_spherePlayer.World);

            // stats 
            if ((DateTime.Now - _timeLastStatCheck).TotalMilliseconds > 1000)
            {
                _tbStats.Text = String.Format("{0} faces in {1} ms. {2} Bodies", _stats.RenderedFaces, _stats.TimeSpentInRendering, _world.Bodies.Count);
                _timeLastStatCheck = DateTime.Now;
            }

        }

        private void UpdatePhysics()
        {
            // step the simulation
            PhysicsSystem.CurrentPhysicsSystem.Integrate(_updateStep);

            // update the objects
            foreach (IPhysicsObject obj in _physicsObjects)
            {
                obj.Draw();
            }
        }

        private PhysicsBox AddABox(Balder.Math.Coordinate position, Balder.Math.Coordinate dimension)
        {
            PhysicsBox box = new PhysicsBox(_world, dimension, Balder.Math.Matrix.Identity, position);
            box.Material = Materials.Crate;
            _physicsObjects.Add(box);
            _container.Children.Add(box);
            //box.Color = new Balder.Color(255, 0, 0, 255);
            return box;
        }

        private PhysicsSphere AddASphere(Balder.Math.Coordinate position, float radius)
        {
            PhysicsSphere sphere = new PhysicsSphere(_world, radius, Balder.Math.Matrix.Identity, position);
            sphere.Material = Materials.Yellow;
            _physicsObjects.Add(sphere);
            _container.Children.Add(sphere);

            return sphere;
        }

        private PhysicsCapsule AddACapsule(Balder.Math.Coordinate position)
        {
            PhysicsCapsule cyl = new PhysicsCapsule(_world, 0.5f, 1f, new Balder.Math.Coordinate(0, 0, 0), position);
            cyl.Material = Materials.Red;
            _physicsObjects.Add(cyl);
            _container.Children.Add(cyl);

            return cyl;
        }

        private PhysicsRagDoll AddARagdoll(Balder.Math.Coordinate position)
        {
            PhysicsRagDoll cyl = new PhysicsRagDoll(_world, PhysicsRagDoll.RagdollType.Simple, 1.0f, _container, _physicsObjects, position);

            // note you can make the ragdoll STAND with: 
            // cyl.PutToSleep();

            return cyl;
        }

        private PhysicsCar AddACar(Balder.Math.Coordinate position)
        {
            PhysicsCar car = new PhysicsCar(_world, _container, _physicsObjects, position);
            return car;
        }
    }
}
